/**
 * Copyright (C) 2011 Andrew C. Love (DNC) <dnc.app.sup@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dnc.cloak.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.apache.log4j.Logger;

import com.dnc.cloak.framework.config.ConfigurationConstants;
import com.dnc.cloak.framework.config.ConfigurationException;
import com.dnc.cloak.framework.util.dataconvert.DataConversionException;
import com.dnc.cloak.framework.util.dataconvert.DataConverter;

public class ConfigurationServiceImpl implements ConfigurationService, ConfigurationConstants {

	private static final Logger logger = Logger.getLogger(ConfigurationServiceImpl.class.getName());
	private SortedMap<String,Properties> properties = new TreeMap<String,Properties>();;
	private String configFileExtension = DEFAULT_FILE_EXTENSION;
	private String propertyDirectory = DEFAULT_FILE_EXTENSION;
	private List<String> ignore = new ArrayList<String>();
	private boolean inJ2EEContainer = false;
		
		
	public ConfigurationServiceImpl() {
		if(logger.isDebugEnabled()){
			logger.debug("ConfigurationServiceImpl constructor");
		}
		initialize();
	}	

	public synchronized void initialize() {
		try {
			ignore.add(".svn");
			loadProperties();
		} 
		catch (ConfigurationException ex) {			
			logger.error("Cloak configuration exception occured during load" ,ex);
		}
		catch (Exception ex) {			
			logger.error(ex);
		}
		catch (Throwable ex) {			
			logger.error(ex);
		}
	}

	
	private synchronized void loadProperties() throws ConfigurationException{		
	    String propertyJarFileName = System.getProperty(PROPERTY_JAR_NAME, DEFAULT_PROPERTY_JAR_NAME); 
		logger.info("Cloak property jar file:" + propertyJarFileName);

		//Load from the jar file
		InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(propertyJarFileName);
	    
		if (inputStream != null) {
			logger.info("Jar found:" + propertyJarFileName);
			loadPropertiesFromJar(inputStream);
		} 
		else {
			logger.info("Jar not found:" + propertyJarFileName);	
		}
		
		//Load from the property directory if it was defined
		propertyDirectory = getSystemProperty(PROPERTY_DIRECTORY,DEFAULT_PROPERTY_DIRECTORY);
		
		logger.info("Cloak property directory: " + propertyDirectory);
		
		loadPropertiesFromDirectory(ROOT_NAMESPACE, propertyDirectory, true);
	}

	private synchronized void loadPropertiesFromJar(InputStream inputStream) throws ConfigurationException {
		JarInputStream jar = null;
		JarEntry jarEntry = null;
		
		try {
			jar = new JarInputStream(inputStream);
			while ((jarEntry = jar.getNextJarEntry()) != null) {
				
				logger.debug("Processing entry:" + jarEntry.getName());
				
				if (!jarEntry.isDirectory() && !ignore.contains(jarEntry.getName())) {

					String extension = jarEntry.getName();
					
					if (extension.lastIndexOf('.')>=0) {
						extension = extension.substring(extension.lastIndexOf('.'));
					}
					
					logger.debug("extension:" + extension);
					
                    String namespace = getNamespace(jarEntry.getName(), PATH_SEP);
                    if (namespace == null || namespace.trim().length() == 0) {
                    	namespace = ROOT_NAMESPACE;
                    }
                    
					logger.debug("namespace:" + namespace);

					if (extension != null && extension.equalsIgnoreCase(configFileExtension)) {
						logger.debug("calling loadPropertiesFromStream");
						loadPropertiesFromStream(namespace,jar);
					}					
				}
			}
		} 
		catch (Exception ex) {
			throw new ConfigurationException(ex);
		} 
		finally {
			if (jar != null) {
				try {
					jar.close();
				} 
				catch (IOException ex) {
					logger.error(ex);
				}
			}
		}		
	}	
	
    private String getNamespace(String name, String pathSeparator) {    	
        int pos = name.lastIndexOf(pathSeparator);
        
        if (pos == -1) {
        	return ROOT_NAMESPACE;
        }
        else {
            String dir = name.substring(0, pos);
            dir = dir.replace(pathSeparator.charAt(0), '.');
        	return getNameSpaceKey(dir);
        }        
    }

    private String getFilename(String name, String pathSeparator) {    	
        int pos = name.lastIndexOf(pathSeparator);
        
        if (pos == -1) {
            return name;
        }
        else {
            return name.substring(++pos);
        }
    }
    
	private synchronized void loadPropertiesFromDirectory(String name, String directory, boolean bRecurse) throws ConfigurationException {		
		File dir = new File(directory);
		
		logger.debug("Processing Namespace:" + name);
		logger.debug("Processing directory:" + directory);	
			
		if (dir.exists()) {
			logger.debug("Directory found:" + directory);	

			String[] files = dir.list();
			
			
			if (files != null) {
				for (int i = 0; i < files.length; i++) {
					File file = new File(directory, files[i]);					
					
					logger.debug("Processing file:" + file.getName());
					
					if (!ignore.contains(file.getName())) {
	
						logger.debug("Not ignored:" + file.getName());
						
						if (file.isDirectory() && bRecurse) {
							logger.debug("is directory:" + file.getName());
							loadPropertiesFromDirectory(name + "." + file.getName(), directory + PATH_SEP + file.getName(), bRecurse);
						} 
						else {
							logger.debug("is file:" + file.getName());
							String strFile = file.getName();
							int pos = strFile.lastIndexOf(".");
							
							if (pos >= 0) {
								logger.debug("extension:" + strFile.substring(pos).trim().toLowerCase());
								if (strFile.substring(pos).trim().toLowerCase().startsWith(configFileExtension.toLowerCase())){
									loadPropertyFile(name, file);
								}
							}
						}
					}
				}
			}
		}
		else {
			logger.info("Directory not found:" + directory);
		}
	}
	
	private synchronized void loadPropertyFile(String name, File file) throws ConfigurationException {		
		InputStream in = null;
		
		logger.debug("loadPropertyFile:name:" + name );
		logger.debug("loadPropertyFile:file:" + file.getName());
		
		try {
			in = new FileInputStream(file);
			loadPropertiesFromStream(name, in);
		} 
		catch (ConfigurationException ex) {
			throw ex;
		} 
		catch (Exception ex) {
			throw new ConfigurationException(ex);
		} 
		finally {
			if (in != null) {
				try {
					in.close();
				} 
				catch (IOException ex) {
					logger.error(ex);
				}
			}
		}
		
	}
	
	private synchronized void loadPropertiesFromStream(String namespace, InputStream stream) throws ConfigurationException {		
		Properties localProperties = null;
		
		try {
			if (logger.isDebugEnabled()) {
				logger.debug("Cloak loading properties from stream for namespace:" + namespace);
			}

			localProperties = properties.get(namespace);
			
			if (localProperties == null) {
				localProperties = new Properties();
				properties.put(namespace, localProperties);
			}
			
			localProperties.load(stream);

		} 
		catch (Exception ex) { 
			throw new ConfigurationException(ex);
		}		
	}
	
	private String getNameSpaceKey(String namespace){
		if (namespace== null){
			return ROOT_NAMESPACE;
		}
		else if (namespace.startsWith(ROOT_NAMESPACE)) {
			return namespace;
		}
		else {
			return ROOT_NAMESPACE + "." + namespace;			
		}
	}

	@Override
	public String dump() {
		StringBuffer sb = new StringBuffer();
		Set<String> namespaces = this.getNameSpaces();
		
		for (String namespace : namespaces) {
			sb.append("\nNamespace:" + namespace);
			sb.append("\n");
			Properties localProperties = getProperties(namespace);
			if (localProperties !=null) {
				sb.append(localProperties.toString());
				sb.append("\n");
			}
			sb.append("\n");
			
		}		
		return sb.toString();		
	}

	@Override
	public String dump(String namespace) {		
		namespace = getNameSpaceKey(namespace);
		
		StringBuffer sb = new StringBuffer();
		sb.append("Namespace:" + namespace);
		sb.append("\n");
		Properties localProperties = getProperties(namespace);
		if (localProperties !=null) {
			sb.append(localProperties.toString());
			sb.append("\n");
		}
	
		return sb.toString();		
	}

	@Override
	public Set<String> getNameSpaces() {
		return properties.keySet();
	}

	@Override
	public Properties getProperties(String namespace) {

		namespace = getNameSpaceKey(namespace);
		
		return properties.get(namespace);
		
	}

	@Override
	public String getProperty(String namespace, String propertyName, String defaultValue) {
		
		
		namespace = getNameSpaceKey(namespace);
		
        Properties localProperties = properties.get(namespace);
        
        if (localProperties != null){
        	
        	String property = localProperties.getProperty(propertyName);

    		if(logger.isDebugEnabled()){
    			logger.debug("property:" + propertyName + ":" +property  + ":" + defaultValue);
    		}

        	return (property == null) ? defaultValue : property;
        	
        }
        else {
        	return defaultValue;
        }        
	} 
	
	@Override
	public Integer getPropertyAsInteger(String namespace, String propertyName, String defaultValue) {
		try {
			return DataConverter.getInteger(getProperty(namespace,propertyName,defaultValue));
		} 
		catch (DataConversionException ex) {
			logger.error(ex);	
			return -1;
		}
	}
	
	@Override
	public String getProperty(String propertyName, String defaultValue) {
		return getProperty(ROOT_NAMESPACE,propertyName, defaultValue);
	}
	
	@Override
	public Integer getPropertyAsInteger(String propertyName, String defaultValue) {
		try {
			return DataConverter.getInteger(getProperty(propertyName,defaultValue));
		} 
		catch (DataConversionException ex) {
			logger.error(ex);	
			return -1;
		}
	}

	@Override
	public void setProperty(String namespace, String propertyName, String defaultValue) {
	
		namespace = getNameSpaceKey(namespace);

		Properties localProperties = properties.get(namespace);
		
		if (localProperties == null) {
			localProperties = new Properties();
			properties.put(namespace, localProperties);
		}
		
		localProperties.put(propertyName,defaultValue);
	}

	@Override
	public void setProperty(String propertyName, String defaultValue) {
		setProperty(ROOT_NAMESPACE,propertyName,defaultValue);
	}

	@Override
	public Set<Object> getPropertyNames() {
		return getPropertyNames(ROOT_NAMESPACE);
	}

	@Override
	public Set<Object> getPropertyNames(String namespace) {
		namespace = getNameSpaceKey(namespace);
		return (properties.get(namespace) != null) ? properties.get(namespace).keySet() : null;
	}

	@Override
	public Properties getSystemProperties() {
		return System.getProperties();
	}

	@Override
	public String getSystemProperty(String key, String defaultValue) {
		return System.getProperties().getProperty(key,defaultValue);
	}

	@Override
	public synchronized boolean isInJ2EEContainer() {
		return inJ2EEContainer;
	}

	@Override
	public synchronized void setInJ2EEContainer(boolean inJ2EEContainer) {
		this.inJ2EEContainer = inJ2EEContainer;
	}
	
}
